package session.TX;

import Helper.TTAHelper.Helper_CustomerLoyalty;
import Helper.TTAHelper.Helper_CustomerValue;
import entity.Outlet;
import entity.OutletReservation;
import entity.PurchasedItem;
import entity.PurchasedItem_Regular;
import entity.Stakeholder;
import entity.StakeholderBill;
import entity.StakeholderFood;
import entity.Users;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

/**
 *
 * @author Lu Xiaodi
 */
@Stateless
public class CustomerAnalysisSB implements CustomerAnalysisSBRemote {

    @PersistenceContext
    EntityManager em;

    //===================Start: outlet customer loyalty methods========================
    //Below are outlet customer loyalty methods
    public List<Helper_CustomerLoyalty> getSortedForeignLoyaltyIdx(Long outletId) {
        List<Helper_CustomerLoyalty> allIdx = this.computeForeignLoyaltyIdx(outletId);
        Collections.sort(allIdx, new Comparator<Helper_CustomerLoyalty>() {

            public int compare(Helper_CustomerLoyalty hc1, Helper_CustomerLoyalty hc2) {
                if (hc2.getLoyaltyIndex() - hc1.getLoyaltyIndex() > 0) {
                    return 1;
                } else if (hc2.getLoyaltyIndex() - hc1.getLoyaltyIndex() < 0) {
                    return -1;
                } else {
                    return 0;
                }
            }
        });
        return allIdx;
    }

    public List<Helper_CustomerLoyalty> getSortedLocalLoyaltyIdx(Long outletId) {
        List<Helper_CustomerLoyalty> allIdx = this.computeLocalLoyaltyIdx(outletId);
        Collections.sort(allIdx, new Comparator<Helper_CustomerLoyalty>() {

            public int compare(Helper_CustomerLoyalty hc1, Helper_CustomerLoyalty hc2) {
                if (hc2.getLoyaltyIndex() - hc1.getLoyaltyIndex() > 0) {
                    return 1;
                } else if (hc2.getLoyaltyIndex() - hc1.getLoyaltyIndex() < 0) {
                    return -1;
                } else {
                    return 0;
                }
            }
        });
        return allIdx;
    }

    public List<Helper_CustomerLoyalty> getSortedForeignLoyaltyIdx(String stakeholderName) {
        List<Helper_CustomerLoyalty> allIdx = this.computeForeignLoyaltyIdx(stakeholderName);
        Collections.sort(allIdx, new Comparator<Helper_CustomerLoyalty>() {

            public int compare(Helper_CustomerLoyalty hc1, Helper_CustomerLoyalty hc2) {
                if (hc2.getLoyaltyIndex() - hc1.getLoyaltyIndex() > 0) {
                    return 1;
                } else if (hc2.getLoyaltyIndex() - hc1.getLoyaltyIndex() < 0) {
                    return -1;
                } else {
                    return 0;
                }
            }
        });
        return allIdx;
    }

    public List<Helper_CustomerLoyalty> getSortedLocalLoyaltyIdx(String stakeholderName) {
        List<Helper_CustomerLoyalty> allIdx = this.computeLocalLoyaltyIdx(stakeholderName);
        Collections.sort(allIdx, new Comparator<Helper_CustomerLoyalty>() {

            public int compare(Helper_CustomerLoyalty hc1, Helper_CustomerLoyalty hc2) {
                if (hc2.getLoyaltyIndex() - hc1.getLoyaltyIndex() > 0) {
                    return 1;
                } else if (hc2.getLoyaltyIndex() - hc1.getLoyaltyIndex() < 0) {
                    return -1;
                } else {
                    return 0;
                }
            }
        });
        return allIdx;
    }
    //=====================End: outlet customer loyalty methods========================

    //=====================Start: CLV========================
    public Double calculateGC(String stakeholderName, Double discountRate, Double expPerRes) {
        return this.computeGC(stakeholderName, discountRate, expPerRes);
    }
    //=====================End: CLV========================

    //=====================Start: customer value methods========================
    public List<Helper_CustomerValue> getSortedForeignCustValue(Long outletId, Double avgExp) {
        return this.computeForeignCustomerValue(outletId, avgExp);
    }

    public List<Helper_CustomerValue> getSortedLocalCustValue(Long outletId, Double avgExp) {
        return this.computeLocalCustomerValue(outletId, avgExp);
    }
    
    public List<Helper_CustomerValue> getSortedForeignCustValue(String stakeholderName, Double avgExp) {
        return this.computeForeignCustomerValue(stakeholderName, avgExp);
    }

    public List<Helper_CustomerValue> getSortedLocalCustValue(String stakeholderName, Double avgExp) {
        return this.computeLocalCustomerValue(stakeholderName, avgExp);
    }
    //======================End: customer value methods=========================

    //++++Start: supporting private methods for outlet customer loyalty calculation++++
    private List<Helper_CustomerLoyalty> computeForeignLoyaltyIdx(String stakeholderName) {
        // get Today's date
        Calendar today = Calendar.getInstance();
        today.setTime(new Date());
        System.out.println("Today: " + today);

        List<Users> foreignCustomers = this.getForeignCustomers();
        List<Helper_CustomerLoyalty> returnList = new ArrayList<Helper_CustomerLoyalty>();

        //for each customer, his loyalty index is calculated this way:
        //x = monthly average visiting times
        //y = number of months since his last visit
        //idx = 12x - y
        for (Users cust : foreignCustomers) {
            double idx = 0;
            double x = 0;
            double y = 0;
            Calendar createTime = Calendar.getInstance();
            createTime.setTime(new Date(cust.getCreateTime()));
            System.out.println("Acct " + cust.getEmail() + " created: " + createTime);
            Integer acctLife = (today.get(Calendar.YEAR) - createTime.get(Calendar.YEAR)) + 1;
            System.out.println(acctLife);

            // Calculate number of purchases
            List<PurchasedItem> purchases = this.getCustomerPurchasesNonDuplicate(cust, stakeholderName);
            if ((purchases != null) && (!purchases.isEmpty())) {
                //x += num of purchases
                x = purchases.size();
                System.out.println("numPurchases:" + purchases.size());
            }
            // Calculate number of reservations
            List<OutletReservation> reservations = this.getCustomerReservations(cust, stakeholderName);
            if ((reservations != null) && (!reservations.isEmpty())) {
                x += reservations.size();
                System.out.println("numReservations:" + reservations.size());
            }

            // Calculate x
            if (x == 0) {
//                idx = Double.MIN_VALUE;
//                Helper_CustomerLoyalty helper = new Helper_CustomerLoyalty(cust.getId(), cust.getEmail(), idx, 4);
//                returnList.add(helper);
                continue;
            }
            x = (double) x / acctLife;
            System.out.println("x:" + x);

            //get the last purchase
            Calendar lastPurchase;
            if ((purchases != null) && (!purchases.isEmpty())) {
                lastPurchase = purchases.get(0).getTime_stamp();
                for (PurchasedItem p : purchases) {
                    Calendar timeSpent = p.getTime_stamp();
                    if (timeSpent.after(lastPurchase)) {
                        lastPurchase = timeSpent;
                    }
                }
            } else {
                lastPurchase = Calendar.getInstance();
                lastPurchase.set(1000, 0, 1);
            }
            //get the last reservation
            Calendar lastReservation;
            if ((reservations != null) && (!reservations.isEmpty())) {
                lastReservation = reservations.get(0).getTimeslot();
                for (OutletReservation or : reservations) {
                    Calendar timeReserve = or.getTimeslot();
                    if (timeReserve.after(lastReservation)) {
                        lastReservation = timeReserve;
                    }
                }
            } else {
                lastReservation = Calendar.getInstance();
                lastReservation.set(1000, 0, 1);
            }
            Calendar lastVisit = (lastPurchase.after(lastReservation)) ? lastPurchase : lastReservation;
            //get #months elapsed since last purchase
            y = (today.get(Calendar.YEAR) - lastVisit.get(Calendar.YEAR));

            System.out.println("y:" + y);
            //calculate idx
            idx = 12 * x - y;

            //restrict idx to 3 decimal 
            Integer type;
            if (idx >= 24) {
                type = 1;
            } else if (idx >= 12) {
                type = 2;
            } else if (idx >= 5) {
                type = 3;
            } else {
                type = 4;
            }
            returnList.add(new Helper_CustomerLoyalty(cust.getId(), cust.getEmail(), idx, type));
        }
        return returnList;
    }

    private List<Helper_CustomerLoyalty> computeForeignLoyaltyIdx(Long outletId) {
        // get Today's date
        Calendar today = Calendar.getInstance();
        today.setTime(new Date());
        System.out.println("Today: " + today);

        List<Users> foreignCustomers = this.getForeignCustomers();
        List<Helper_CustomerLoyalty> returnList = new ArrayList<Helper_CustomerLoyalty>();

        //for each customer, his loyalty index is calculated this way:
        //x = monthly average visiting times
        //y = number of months since his last visit
        //idx = 12x - y
        for (Users cust : foreignCustomers) {
            double idx = 0;
            double x = 0;
            double y = 0;
            Calendar createTime = Calendar.getInstance();
            createTime.setTime(new Date(cust.getCreateTime()));
            System.out.println("Acct " + cust.getEmail() + " created: " + createTime);
            Integer acctLife = (today.get(Calendar.YEAR) - createTime.get(Calendar.YEAR)) + 1;
            System.out.println(acctLife);

            // Calculate number of purchases
            List<PurchasedItem_Regular> purchases = this.getCustomerPurchasesNonDuplicate(cust, outletId);
            if ((purchases != null) && (!purchases.isEmpty())) {
                //x += num of purchases
                x = purchases.size();
                System.out.println("numPurchases:" + purchases.size());
            }
            // Calculate number of reservations
            List<OutletReservation> reservations = this.getCustomerReservations(cust, outletId);
            if ((reservations != null) && (!reservations.isEmpty())) {
                x += reservations.size();
                System.out.println("numReservations:" + reservations.size());
            }

            // Calculate x
            if (x == 0) {
//                idx = Double.MIN_VALUE;
//                Helper_CustomerLoyalty helper = new Helper_CustomerLoyalty(cust.getId(), cust.getEmail(), idx, 4);
//                returnList.add(helper);
                continue;
            }
            x = (double) x / acctLife;
            System.out.println("x:" + x);

            //get the last purchase
            Calendar lastPurchase;
            if ((purchases != null) && (!purchases.isEmpty())) {
                lastPurchase = purchases.get(0).getTime_stamp();
                for (PurchasedItem_Regular p : purchases) {
                    Calendar timeSpent = p.getTime_stamp();
                    if (timeSpent.after(lastPurchase)) {
                        lastPurchase = timeSpent;
                    }
                }
            } else {
                lastPurchase = Calendar.getInstance();
                lastPurchase.set(1000, 0, 1);
            }
            //get the last reservation
            Calendar lastReservation;
            if ((reservations != null) && (!reservations.isEmpty())) {
                lastReservation = reservations.get(0).getTimeslot();
                for (OutletReservation or : reservations) {
                    Calendar timeReserve = or.getTimeslot();
                    if (timeReserve.after(lastReservation)) {
                        lastReservation = timeReserve;
                    }
                }
            } else {
                lastReservation = Calendar.getInstance();
                lastReservation.set(1000, 0, 1);
            }
            Calendar lastVisit = (lastPurchase.after(lastReservation)) ? lastPurchase : lastReservation;
            //get #months elapsed since last purchase
            y = (today.get(Calendar.YEAR) - lastVisit.get(Calendar.YEAR));

            System.out.println("y:" + y);
            //calculate idx
            idx = 12 * x - y;

            //restrict idx to 3 decimal 
            Integer type;
            if (idx >= 24) {
                type = 1;
            } else if (idx >= 12) {
                type = 2;
            } else if (idx >= 5) {
                type = 3;
            } else {
                type = 4;
            }
            returnList.add(new Helper_CustomerLoyalty(cust.getId(), cust.getEmail(), idx, type));
        }
        return returnList;
    }

    private List<Helper_CustomerLoyalty> computeLocalLoyaltyIdx(String stakeholderName) {
        // get Today's date
        Calendar today = Calendar.getInstance();
        today.setTime(new Date());
        System.out.println("Today: " + today);

        List<Users> localCustomers = this.getLocalCustomers();
        List<Helper_CustomerLoyalty> returnList = new ArrayList<Helper_CustomerLoyalty>();

        //for each customer, his loyalty index is calculated this way:
        //x = monthly average visiting times
        //y = number of months since his last visit
        //idx = 12x - y
        for (Users cust : localCustomers) {
            double idx = 0;
            double x = 0;
            double y = 0;
            Calendar createTime = Calendar.getInstance();
            createTime.setTime(new Date(cust.getCreateTime()));
            System.out.println("Acct " + cust.getEmail() + " created: " + createTime);
            Integer acctLife = (today.get(Calendar.YEAR) - createTime.get(Calendar.YEAR)) * 12
                    + today.get(Calendar.MONTH) - createTime.get(Calendar.MONTH) + 1;
            System.out.println(acctLife);

            // Calculate number of purchases
            List<PurchasedItem> purchases = this.getCustomerPurchasesNonDuplicate(cust, stakeholderName);
            if ((purchases != null) && (!purchases.isEmpty())) {
                //x += num of purchases
                x = purchases.size();
                System.out.println("numPurchases:" + purchases.size());
            }
            // Calculate number of reservations
            List<OutletReservation> reservations = this.getCustomerReservations(cust, stakeholderName);
            if ((reservations != null) && (!reservations.isEmpty())) {
                x += reservations.size();
                System.out.println("numReservations:" + reservations.size());
            }

            // Calculate x
            if (x == 0) {
//                idx = Double.MIN_VALUE;
//                Helper_CustomerLoyalty helper = new Helper_CustomerLoyalty(cust.getId(), cust.getEmail(), idx, 4);
//                returnList.add(helper);
                continue;
            }
            x = (double) x / acctLife;
            System.out.println("x:" + x);

            //get the last purchase
            Calendar lastPurchase;
            if ((purchases != null) && (!purchases.isEmpty())) {
                lastPurchase = purchases.get(0).getTime_stamp();
                for (PurchasedItem p : purchases) {
                    Calendar timeSpent = p.getTime_stamp();
                    if (timeSpent.after(lastPurchase)) {
                        lastPurchase = timeSpent;
                    }
                }
            } else {
                lastPurchase = Calendar.getInstance();
                lastPurchase.set(1000, 0, 1);
            }
            //get the last reservation
            Calendar lastReservation;
            if ((reservations != null) && (!reservations.isEmpty())) {
                lastReservation = reservations.get(0).getTimeslot();
                for (OutletReservation or : reservations) {
                    Calendar timeReserve = or.getTimeslot();
                    if (timeReserve.after(lastReservation)) {
                        lastReservation = timeReserve;
                    }
                }
            } else {
                lastReservation = Calendar.getInstance();
                lastReservation.set(1000, 0, 1);
            }
            Calendar lastVisit = (lastPurchase.after(lastReservation)) ? lastPurchase : lastReservation;
            //get #months elapsed since last purchase
            y = (today.get(Calendar.YEAR) - lastVisit.get(Calendar.YEAR)) * 12
                    + today.get(Calendar.MONTH) - lastVisit.get(Calendar.MONTH);

            System.out.println("y:" + y);
            //calculate idx
            idx = 12 * x - y;

            //restrict idx to 3 decimal 
            Integer type;
            if (idx >= 6) {
                type = 1;
            } else if (idx >= 3) {
                type = 2;
            } else if (idx >= 0) {
                type = 3;
            } else {
                type = 4;
            }
            returnList.add(new Helper_CustomerLoyalty(cust.getId(), cust.getEmail(), idx, type));
        }
        return returnList;
    }

    private List<Helper_CustomerLoyalty> computeLocalLoyaltyIdx(Long outletId) {
        // get Today's date
        Calendar today = Calendar.getInstance();
        today.setTime(new Date());
        System.out.println("Today: " + today);

        List<Users> localCustomers = this.getLocalCustomers();
        List<Helper_CustomerLoyalty> returnList = new ArrayList<Helper_CustomerLoyalty>();

        //for each customer, his loyalty index is calculated this way:
        //x = monthly average visiting times
        //y = number of months since his last visit
        //idx = 12x - y
        for (Users cust : localCustomers) {
            double idx = 0;
            double x = 0;
            double y = 0;
            Calendar createTime = Calendar.getInstance();
            createTime.setTime(new Date(cust.getCreateTime()));
            System.out.println("Acct " + cust.getEmail() + " created: " + createTime);
            Integer acctLife = (today.get(Calendar.YEAR) - createTime.get(Calendar.YEAR)) * 12
                    + today.get(Calendar.MONTH) - createTime.get(Calendar.MONTH) + 1;
            System.out.println(acctLife);

            // Calculate number of purchases
            List<PurchasedItem_Regular> purchases = this.getCustomerPurchasesNonDuplicate(cust, outletId);
            if ((purchases != null) && (!purchases.isEmpty())) {
                //x += num of purchases
                x = purchases.size();
                System.out.println("numPurchases:" + purchases.size());
            }
            // Calculate number of reservations
            List<OutletReservation> reservations = this.getCustomerReservations(cust, outletId);
            if ((reservations != null) && (!reservations.isEmpty())) {
                x += reservations.size();
                System.out.println("numReservations:" + reservations.size());
            }

            // Calculate x
            if (x == 0) {
//                idx = Double.MIN_VALUE;
//                Helper_CustomerLoyalty helper = new Helper_CustomerLoyalty(cust.getId(), cust.getEmail(), idx, 4);
//                returnList.add(helper);
                continue;
            }
            x = (double) x / acctLife;
            System.out.println("x:" + x);

            //get the last purchase
            Calendar lastPurchase;
            if ((purchases != null) && (!purchases.isEmpty())) {
                lastPurchase = purchases.get(0).getTime_stamp();
                for (PurchasedItem_Regular p : purchases) {
                    Calendar timeSpent = p.getTime_stamp();
                    if (timeSpent.after(lastPurchase)) {
                        lastPurchase = timeSpent;
                    }
                }
            } else {
                lastPurchase = Calendar.getInstance();
                lastPurchase.set(1000, 0, 1);
            }
            //get the last reservation
            Calendar lastReservation;
            if ((reservations != null) && (!reservations.isEmpty())) {
                lastReservation = reservations.get(0).getTimeslot();
                for (OutletReservation or : reservations) {
                    Calendar timeReserve = or.getTimeslot();
                    if (timeReserve.after(lastReservation)) {
                        lastReservation = timeReserve;
                    }
                }
            } else {
                lastReservation = Calendar.getInstance();
                lastReservation.set(1000, 0, 1);
            }
            Calendar lastVisit = (lastPurchase.after(lastReservation)) ? lastPurchase : lastReservation;
            //get #months elapsed since last purchase
            y = (today.get(Calendar.YEAR) - lastVisit.get(Calendar.YEAR)) * 12
                    + today.get(Calendar.MONTH) - lastVisit.get(Calendar.MONTH);

            System.out.println("y:" + y);
            //calculate idx
            idx = 12 * x - y;

            //restrict idx to 3 decimal 
            Integer type;
            if (idx >= 6) {
                type = 1;
            } else if (idx >= 3) {
                type = 2;
            } else if (idx >= 0) {
                type = 3;
            } else {
                type = 4;
            }
            returnList.add(new Helper_CustomerLoyalty(cust.getId(), cust.getEmail(), idx, type));
        }
        return returnList;
    }

    private List<OutletReservation> getCustomerReservations(Users user, Long outletId) {
        Long userId = user.getId();
        Query query = em.createQuery("SELECT r FROM OutletReservation r WHERE "
                + "r.customer.id= :userId AND r.outlet.id = :outletId AND r.customerShowUp = true");
        query.setParameter("userId", userId);
        query.setParameter("outletId", outletId);
        return query.getResultList();
    }

    private List<OutletReservation> getCustomerReservations(Users user, String stakeholderName) {
        Long userId = user.getId();
        Query sQuery = em.createQuery("SELECT s FROM StakeholderFood s WHERE s.name = :sName");
        sQuery.setParameter("sName", stakeholderName);
        List<StakeholderFood> stakeholders = sQuery.getResultList();
        if (stakeholders == null || stakeholders.isEmpty()) {
            return null;
        }
        StakeholderFood sFood = stakeholders.get(0);

        List<Outlet> outlets = sFood.getOutlet();
        List<OutletReservation> returnList = new ArrayList<OutletReservation>();
        for (Outlet o : outlets) {
            Long outletId = o.getId();
            Query query = em.createQuery("SELECT r FROM OutletReservation r WHERE "
                    + "r.customer.id= :userId AND r.outlet.id = :outletId AND r.customerShowUp = true");
            query.setParameter("userId", userId);
            query.setParameter("outletId", outletId);
            returnList.addAll(query.getResultList());
        }
        return returnList;
    }

    private List<PurchasedItem_Regular> getCustomerPurchases(Users user, Long outletId) {
        String email = user.getEmail();
        Query query = em.createQuery("SELECT p FROM PurchasedItem_Regular p WHERE "
                + "p.customerEmail = :email AND p.outlet_id = :outletId GROUP BY p.customerEmail, p.time_stamp");
        query.setParameter("email", email);
        query.setParameter("outletId", outletId);
        List<PurchasedItem_Regular> returnList = query.getResultList();
        return returnList;
    }

    private List<PurchasedItem_Regular> getCustomerPurchasesNonDuplicate(Users user, Long outletId) {
        List<PurchasedItem_Regular> itemList = this.getCustomerPurchases(user, outletId);
        if (itemList == null || itemList.isEmpty()) {
            return itemList;
        }
        List<PurchasedItem_Regular> returnList = new ArrayList<PurchasedItem_Regular>();
        returnList.add(itemList.get(0));
        Calendar createTime = itemList.get(0).getTime_stamp();
        for (int i = 0; i < itemList.size(); i++) {
            PurchasedItem_Regular aPurchasedItem = itemList.get(i);
            if (!aPurchasedItem.getTime_stamp().equals(createTime)) {
                createTime = aPurchasedItem.getTime_stamp();
                returnList.add(aPurchasedItem);
            }
        }
        return returnList;
    }

    private List<PurchasedItem> getCustomerPurchases(Users user, String stakeholderName) {
        String email = user.getEmail();
        Query query = em.createQuery("SELECT p FROM PurchasedItem p WHERE "
                + "p.customerEmail = :email AND p.seller = :sName GROUP BY p.customerEmail, p.time_stamp");
        query.setParameter("email", email);
        query.setParameter("sName", stakeholderName);
        List<PurchasedItem> returnList = query.getResultList();
        return returnList;
    }

    private List<PurchasedItem> getCustomerPurchasesNonDuplicate(Users user, String stakeholderName) {
        List<PurchasedItem> itemList = this.getCustomerPurchases(user, stakeholderName);
        if (itemList == null || itemList.isEmpty()) {
            return itemList;
        }
        List<PurchasedItem> returnList = new ArrayList<PurchasedItem>();
        returnList.add(itemList.get(0));
        Calendar createTime = itemList.get(0).getTime_stamp();
        for (int i = 0; i < itemList.size(); i++) {
            PurchasedItem aPurchasedItem = itemList.get(i);
            if (!aPurchasedItem.getTime_stamp().equals(createTime)) {
                createTime = aPurchasedItem.getTime_stamp();
                returnList.add(aPurchasedItem);
            }
        }
        return returnList;
    }

    private List<Users> getLocalCustomers() {
        Query query = em.createQuery("SELECT u FROM Users u WHERE u.country = :singapore");
        query.setParameter("singapore", "Singapore");
        return query.getResultList();
    }

    private List<Users> getForeignCustomers() {
        Query query = em.createQuery("SELECT u FROM Users u WHERE u.country <> :singapore");
        query.setParameter("singapore", "Singapore");
        return query.getResultList();
    }

//    private Set<Users> getLocalCustomers(Long outletId){
//        Query query = em.createQuery("SELECT p FROM PurchasedItem_Regular p WHERE p.outlet_id = :oId AND p.buyer_country = :singapore");
//        query.setParameter("oId", outletId);
//        query.setParameter("singapore", "Singapore");
//        List<PurchasedItem_Regular> list= query.getResultList();
//        
//    }
    //+++++End: supporting private methods for outlet customer loyalty calculation+++++
    //+++++++++++++++Start: supporting private methods for Customer Value++++++++++++++
    private List<Helper_CustomerValue> computeForeignCustomerValue(Long outletId, Double avgExp) {
        // get Today's date
        Calendar today = Calendar.getInstance();
        today.setTime(new Date());
        System.out.println("Today: " + today);

        List<Users> foreignCustomers = this.getForeignCustomers();
        List<Helper_CustomerValue> returnList = new ArrayList<Helper_CustomerValue>();

        //for each customer, his value index is calculated this way:
        //sum (exp(n)/#month passed(n)
        for (Users cust : foreignCustomers) {
            double idx = 0;

            // Calculate number of purchases
            List<PurchasedItem_Regular> purchases = this.getCustomerPurchases(cust, outletId);
            if ((purchases != null) && (!purchases.isEmpty())) {
                for (PurchasedItem_Regular p : purchases) {
                    Calendar timeStamp = p.getTime_stamp();
                    int monthDiff = this.computeMonthDiff(timeStamp, today) + 1;
                    idx += p.getSubtotal() / monthDiff;
                }
                System.out.println("Purchase value:" + idx);
            }
            // Calculate number of reservations
            List<OutletReservation> reservations = this.getCustomerReservations(cust, outletId);
            if ((reservations != null) && (!reservations.isEmpty())) {
                for (OutletReservation r : reservations) {
                    Calendar timeStamp = r.getTimeslot();
                    int monthDiff = this.computeMonthDiff(timeStamp, today);
                    if (monthDiff == 0) {
                        idx += avgExp;
                    } else {
                        idx += avgExp / monthDiff;
                    }
                }
                System.out.println("numReservations:" + reservations.size());
            }
            if (idx >= 0.001) {
                returnList.add(new Helper_CustomerValue(cust.getId(), cust.getEmail(), idx));
            }
        }
        Collections.sort(returnList, Collections.reverseOrder());
        return returnList;
    }

    private List<Helper_CustomerValue> computeForeignCustomerValue(String stakeholderName, Double avgExp) {
        // get Today's date
        Calendar today = Calendar.getInstance();
        today.setTime(new Date());
        System.out.println("Today: " + today);

        List<Users> foreignCustomers = this.getForeignCustomers();
        List<Helper_CustomerValue> returnList = new ArrayList<Helper_CustomerValue>();

        //for each customer, his value index is calculated this way:
        //sum (exp(n)/#month passed(n)
        for (Users cust : foreignCustomers) {
            double idx = 0;

            // Calculate number of purchases
            List<PurchasedItem> purchases = this.getCustomerPurchases(cust, stakeholderName);
            if ((purchases != null) && (!purchases.isEmpty())) {
                for (PurchasedItem p : purchases) {
                    Calendar timeStamp = p.getTime_stamp();
                    int monthDiff = this.computeMonthDiff(timeStamp, today) + 1;
                    idx += p.getSubtotal() / monthDiff;
                }
                System.out.println("Purchase value:" + idx);
            }
            // Calculate number of reservations
            List<OutletReservation> reservations = this.getCustomerReservations(cust, stakeholderName);
            if ((reservations != null) && (!reservations.isEmpty())) {
                for (OutletReservation r : reservations) {
                    Calendar timeStamp = r.getTimeslot();
                    int monthDiff = this.computeMonthDiff(timeStamp, today);
                    if (monthDiff == 0) {
                        idx += avgExp;
                    } else {
                        idx += avgExp / monthDiff;
                    }
                }
                System.out.println("numReservations:" + reservations.size());
            }
            if (idx >= 0.001) {
                returnList.add(new Helper_CustomerValue(cust.getId(), cust.getEmail(), idx));
            }
        }
        Collections.sort(returnList, Collections.reverseOrder());
        return returnList;
    }

    private List<Helper_CustomerValue> computeLocalCustomerValue(Long outletId, Double avgExp) {
        // get Today's date
        Calendar today = Calendar.getInstance();
        today.setTime(new Date());
        System.out.println("Today: " + today);

        List<Users> localCustomers = this.getLocalCustomers();
        List<Helper_CustomerValue> returnList = new ArrayList<Helper_CustomerValue>();

        //for each customer, his value index is calculated this way:
        //sum (exp(n)/#month passed(n)
        for (Users cust : localCustomers) {
            double idx = 0;

            // Calculate number of purchases
            List<PurchasedItem_Regular> purchases = this.getCustomerPurchases(cust, outletId);
            if ((purchases != null) && (!purchases.isEmpty())) {
                for (PurchasedItem_Regular p : purchases) {
                    Calendar timeStamp = p.getTime_stamp();
                    int monthDiff = this.computeMonthDiff(timeStamp, today) + 1;
                    idx += p.getSubtotal() / monthDiff;
                }
                System.out.println("Purchase value:" + idx);
            }
            // Calculate number of reservations
            List<OutletReservation> reservations = this.getCustomerReservations(cust, outletId);
            if ((reservations != null) && (!reservations.isEmpty())) {
                for (OutletReservation r : reservations) {
                    Calendar timeStamp = r.getTimeslot();
                    int monthDiff = this.computeMonthDiff(timeStamp, today);
                    if (monthDiff == 0) {
                        idx += avgExp;
                    } else {
                        idx += avgExp / monthDiff;
                    }
                }
                System.out.println("numReservations:" + reservations.size());
            }
            if (idx >= 0.001) {
                returnList.add(new Helper_CustomerValue(cust.getId(), cust.getEmail(), idx));
            }
        }
        Collections.sort(returnList, Collections.reverseOrder());
        return returnList;
    }

    private List<Helper_CustomerValue> computeLocalCustomerValue(String stakeholderName, Double avgExp) {
        // get Today's date
        Calendar today = Calendar.getInstance();
        today.setTime(new Date());
        System.out.println("Today: " + today);

        List<Users> localCustomers = this.getLocalCustomers();
        List<Helper_CustomerValue> returnList = new ArrayList<Helper_CustomerValue>();

        //for each customer, his value index is calculated this way:
        //sum (exp(n)/#month passed(n)
        for (Users cust : localCustomers) {
            double idx = 0;

            // Calculate number of purchases
            List<PurchasedItem> purchases = this.getCustomerPurchases(cust, stakeholderName);
            if ((purchases != null) && (!purchases.isEmpty())) {
                for (PurchasedItem p : purchases) {
                    Calendar timeStamp = p.getTime_stamp();
                    int monthDiff = this.computeMonthDiff(timeStamp, today) + 1;
                    idx += p.getSubtotal() / monthDiff;
                }
                System.out.println("Purchase value:" + idx);
            }
            // Calculate number of reservations
            List<OutletReservation> reservations = this.getCustomerReservations(cust, stakeholderName);
            if ((reservations != null) && (!reservations.isEmpty())) {
                for (OutletReservation r : reservations) {
                    Calendar timeStamp = r.getTimeslot();
                    int monthDiff = this.computeMonthDiff(timeStamp, today);
                    if (monthDiff == 0) {
                        idx += avgExp;
                    } else {
                        idx += avgExp / monthDiff;
                    }
                }
                System.out.println("numReservations:" + reservations.size());
            }
            if (idx >= 0.001) {
                returnList.add(new Helper_CustomerValue(cust.getId(), cust.getEmail(), idx));
            }
        }
        Collections.sort(returnList, Collections.reverseOrder());
        return returnList;
    }

    private int computeMonthDiff(Calendar s, Calendar e) {
        return (e.get(Calendar.YEAR) - s.get(Calendar.YEAR)) * 12
                + e.get(Calendar.MONTH) - s.get(Calendar.MONTH);
    }
    //++++++++++++++++End: supporting private methods for Customer Value+++++++++++++++

    //++++++++++++++++++++Start: supporting private methods for CLV++++++++++++++++++++
    private double computeGC(String stakeholderName, Double discountRate, Double expPerRes) {
        int allCust = 0;
        int numCustRetained = 0;
        double purchaseIncome = 0;
        double exp = 0;
        double reservationIncome = 0;
        double profit = 0;
        double retensionRate = 0;

        double gc;

        Stakeholder stakeholder = em.find(Stakeholder.class, stakeholderName);
        if (stakeholder == null) {
            return 0;
        }
        List<StakeholderBill> last25Bills = this.getLast25StakeholderBills(stakeholderName);
        Calendar[] lastYear = this.getLastYearStartEnd(last25Bills);
        if (lastYear == null) {
            return 99999;
        }
        Calendar[] yearBeforeLast = this.getYearBeforeLastStartEnd(last25Bills);
        if (yearBeforeLast == null) {
            return Double.MAX_VALUE;
        }

        purchaseIncome = this.getPurchaseIncomeLastYear(last25Bills);
        exp = this.getExpLastYear(last25Bills);
        System.out.println("purchaseIncome:" + purchaseIncome);
        System.out.println("exp:" + exp);
        reservationIncome = this.getReservationNumber(lastYear[0],
                lastYear[1], stakeholder) * expPerRes;
        System.out.println("reservation income:" + reservationIncome);
        profit = purchaseIncome + reservationIncome - exp;
        System.out.println("profit:" + profit);

        Set<String> custLastYear = this.getCustList(lastYear[0],
                lastYear[1], stakeholder);
        System.out.println(custLastYear.size());
        Set<String> custYearBeforeLast = this.getCustList(yearBeforeLast[0],
                yearBeforeLast[1], stakeholder);
        System.out.println(custYearBeforeLast.size());

        allCust = custYearBeforeLast.size();
        for (String s : custYearBeforeLast) {
            if (custLastYear.contains(s)) {
                numCustRetained++;
            }
        }
        retensionRate = ((double) numCustRetained) / allCust;
        if (retensionRate > 0.9999999) {
            return Double.MAX_VALUE;
        }

        gc = profit / custLastYear.size();

        return gc * (retensionRate / (1 + discountRate - retensionRate));
    }

    private Calendar[] getLastYearStartEnd(List<StakeholderBill> last25Bills) {
        Calendar[] result = new Calendar[2];
        Calendar start = Calendar.getInstance();
        if (last25Bills == null || last25Bills.isEmpty()) {
            return null;
        }
        if (last25Bills.size() >= 13) {
            start.setTime(last25Bills.get(12).getBillDate());
        } else {
            start.set(1000, 0, 1);
        }
        Calendar end = Calendar.getInstance();
        end.setTime(last25Bills.get(0).getBillDate());
        result[0] = start;
        result[1] = end;
        return result;
    }

    private Calendar[] getYearBeforeLastStartEnd(List<StakeholderBill> last25Bills) {
        Calendar[] result = new Calendar[2];
        Calendar start = Calendar.getInstance();
        Calendar end = Calendar.getInstance();
        if (last25Bills == null || last25Bills.size() < 13) {
            return null;
        }
        if (last25Bills.size() == 25) {
            end.setTime(last25Bills.get(12).getBillDate());
            start.setTime(last25Bills.get(24).getBillDate());
        } else {
            start.set(1000, 0, 1);
        }
        result[0] = start;
        result[1] = end;
        return result;
    }

    private Double getPurchaseIncomeLastYear(List<StakeholderBill> last25Bills) {
        Double purchaseIncome = (double) 0;
        for (int i = 0; i < last25Bills.size(); i++) {
            if (i < 12) {
                purchaseIncome += last25Bills.get(i).getReInbursement();
            }
        }
        return purchaseIncome;
    }

    //return a list of customer emails (with no duplicates) who is the given stakeholder's customer.
    private Set<String> getCustList(Calendar start, Calendar end, Stakeholder stakeholder) {
        Set<String> purchaseCust = this.getPurchaseCust(start, end, stakeholder);
        Set<String> reservationCust;
        if (stakeholder instanceof StakeholderFood) {
            reservationCust = this.getReservationCust(start, end, (StakeholderFood) stakeholder);
            purchaseCust.addAll(reservationCust);
        }
        return purchaseCust;
    }

    //return a list of customer emails (with no duplicates) who made reservations last year.
    private Set<String> getReservationCust(Calendar start, Calendar end, StakeholderFood stakeholder) {
        List<Outlet> outlets = stakeholder.getOutlet();
        Set<String> custEmails = new HashSet<String>();
        long numReservations = 0;
        for (Outlet o : outlets) {
            Long oId = o.getId();
            Query query = em.createQuery("SELECT res FROM OutletReservation res WHERE res.outlet.id = :oId AND "
                    + "res.timeslot >= :start AND res.timeslot <= :end");
            query.setParameter("oId", oId);
            query.setParameter("start", start);
            query.setParameter("end", end);
            List<OutletReservation> orForAnOutlet = query.getResultList();
            numReservations += orForAnOutlet.size();
            if (!(orForAnOutlet == null || orForAnOutlet.isEmpty())) {
                for (OutletReservation or : orForAnOutlet) {
                    custEmails.add(or.getCustomer().getEmail().trim().toLowerCase());
                }
            }
        }
        return custEmails;
    }

    private long getReservationNumber(Calendar start, Calendar end, Stakeholder stakeholder) {
        long numReservations = 0;
        if (!(stakeholder instanceof StakeholderFood)) {
            return 0;
        }
        List<Outlet> outlets = ((StakeholderFood) stakeholder).getOutlet();
        for (Outlet o : outlets) {
            Long oId = o.getId();
            Query query = em.createQuery("SELECT res FROM OutletReservation res WHERE res.outlet.id = :oId AND "
                    + "res.timeslot >= :start AND res.timeslot <= :end");
            query.setParameter("oId", oId);
            query.setParameter("start", start);
            query.setParameter("end", end);
            List<OutletReservation> orForAnOutlet = query.getResultList();
            if (orForAnOutlet != null) {
                numReservations += orForAnOutlet.size();
            }
        }
        return numReservations;
    }

    private Set<String> getPurchaseCust(Calendar start, Calendar end, Stakeholder stakeholder) {
        //get all users who make purchases within the time range
        Set<String> custEmails = new HashSet<String>();
        Query q1 = em.createQuery("SELECT p FROM PurchasedItem p WHERE p.seller = :st AND "
                + "p.time_stamp >= :start AND p.time_stamp <= :end ORDER BY p.customerEmail");
        q1.setParameter("st", stakeholder.getName());
        q1.setParameter("start", start);
        q1.setParameter("end", end);
        List<PurchasedItem> allPurchasedItems = q1.getResultList();
        if (!(allPurchasedItems == null)) {
            for (PurchasedItem p : allPurchasedItems) {
                custEmails.add(p.getCustomerEmail().toLowerCase().trim());
            }
        }
        System.out.println(custEmails.size());
        return custEmails;
    }

    private Double getExpLastYear(List<StakeholderBill> last25Bills) {
        Double exp = (double) 0;
        for (int i = 0; i < last25Bills.size(); i++) {
            if (i < 12) {
                exp += last25Bills.get(i).getSubTotal();
            }
        }
        return exp;
    }

    private List<StakeholderBill> getLast25StakeholderBills(String stakeholderName) {
        Query query = em.createQuery("SELECT b FROM StakeholderBill b "
                + "WHERE b.stakeHolderName = :stakeholderName ORDER BY b.billDate DESC");
        query.setParameter("stakeholderName", stakeholderName);
        List<StakeholderBill> allBills = query.getResultList();
        List<StakeholderBill> returnBills = new ArrayList<StakeholderBill>();
//        for (StakeholderBill b : allBills) {
//            System.out.println(b.getId());
//        }
        if (allBills.size() < 25) {
            return allBills;
        } else {
            for (int i = 0; i < 25; i++) {
                returnBills.add(allBills.get(i));
            }
            return returnBills;
        }

    }
    //+++++++++++++++++++End: supporting private methods for CLV++++++++++++++++++++++++

    //*********************Start: appclient test methods********************************
    public List<String> getLocalCustomers_Test() {
        List<Users> list = this.getLocalCustomers();
        List<String> returnList = new ArrayList<String>();
        for (Users u : list) {
            returnList.add("Custid:" + u.getId() + "; email:" + u.getEmail() + "; country:" + u.getCountry());
        }
        return returnList;
    }

    public List<String> getForeignCustomers_Test() {
        List<Users> list = this.getForeignCustomers();
        List<String> returnList = new ArrayList<String>();
        for (Users u : list) {
            returnList.add("Custid:" + u.getId() + "; email:" + u.getEmail() + "; country:" + u.getCountry());
        }
        return returnList;
    }

    public List<String> getCustomerPurchases_Test(String customerEmail, Long outletId) {
        Query query = em.createQuery("SELECT u FROM Users u WHERE u.email = :email");
        query.setParameter("email", customerEmail);
        Users user = (Users) query.getResultList().get(0);
        List<PurchasedItem_Regular> itemList = this.getCustomerPurchases(user, outletId);
        List<String> itemStr = new ArrayList<String>();
        for (PurchasedItem_Regular p : itemList) {
            itemStr.add("PurchasedItemId:" + p.getId() + "; foodItemId:" + p.getItem_id()
                    + "; foodItemName:" + p.getItem_name()
                    + "; customeEmail:" + p.getCustomerEmail());
        }
        return itemStr;
    }

    public List<String> getCustomerReservation_Test(Long customerId, Long outletId) {
        Users user = em.find(Users.class, customerId);
        if (user == null) {
            return null;
        }
        List<OutletReservation> reservations = this.getCustomerReservations(user, outletId);
        List<String> returnList = new ArrayList<String>();
        for (OutletReservation r : reservations) {
            returnList.add("OutletReservationId:" + r.getId() + "; timeSlot:" + r.getTimeslot()
                    + "; showUp:" + r.getCustomerShowUp());
        }
        return returnList;
    }

    public Set<String> getCustEmail() {
        Calendar c = Calendar.getInstance();
        c.set(2010, 10, 01);
        Query query = em.createQuery("SELECT p FROM PurchasedItem p WHERE p.seller= :seller AND"
                + " p.time_stamp> :time");
        query.setParameter("seller", "Mcdonald");
        query.setParameter("time", c);
        List<PurchasedItem> list = query.getResultList();
        Set<String> returnSet = new HashSet<String>();
        for (PurchasedItem p : list) {
            returnSet.add(p.getCustomerEmail().trim().toLowerCase());
        }
        return returnSet;
    }

    public Set<String> getCustEmail2() {
        Calendar c = Calendar.getInstance();
        c.set(2010, 10, 01);
        Query query = em.createQuery("SELECT p FROM PurchasedItem p WHERE p.seller= :seller AND"
                + " p.time_stamp<= :time");
        query.setParameter("seller", "Mcdonald");
        query.setParameter("time", c);
        List<PurchasedItem> list = query.getResultList();
        Set<String> returnSet = new HashSet<String>();
        for (PurchasedItem p : list) {
            returnSet.add(p.getCustomerEmail().trim().toLowerCase());
        }
        return returnSet;
    }
    //**********************End: appclient test methods*******************************
}
